﻿using System.Runtime.CompilerServices;
using HelenServer.UserCenter.Authentication;
using HelenServer.UserCenter.Contracts;
using IdentityModel.Client;
using Newtonsoft.Json.Linq;

namespace HelenServer.Authentication.OAuth2;

[ServiceProvider(typeof(IAuthenticationService), "OAuth2")]
internal class OAuth2AuthenticationService : IAuthenticationService
{
    internal const string SOURCE_PREFIX = "source:";

    private readonly HttpClient _client;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly CredentialsOptions _credentialsOptions;

    public OAuth2AuthenticationService(OAuthEndpointService oauthService, IHttpContextAccessor httpContextAccessor, IOptions<CredentialsOptions> options)
    {
        _client = oauthService.GetHttpClient();
        _httpContextAccessor = httpContextAccessor;
        _credentialsOptions = options.Value;
    }

    public async Task<OperationResult<string>> LoginAsync(Operation<LoginModel> operation, CancellationToken cancellationToken)
    {
        var model = operation.Parameter;

        var context = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException();

        var content = BuildPasswordEncodedContent(model.Username, model.Password, _credentialsOptions.Scope);

        var disco = await _client.GetDiscoveryDocumentAsync($"{context.Request.Scheme}://{context.Request.Host}", cancellationToken);

        var response = await _client.PostAsync(disco.TokenEndpoint, content, cancellationToken);

        var loginResponse = await response.Content.ReadAsStringAsync(cancellationToken: cancellationToken);
        var jsonObj = JObject.Parse(loginResponse);

        var error = jsonObj.Value<string>("error");

        if (!string.IsNullOrWhiteSpace(error))
        {
            context.Response.StatusCode = StatusCodes.Status400BadRequest;

            return OperationResult<string>.Failed(error);
        }
        else
        {
            return OperationResult<string>.OK(jsonObj.Value<string>("access_token"));
        }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static FormUrlEncodedContent BuildPasswordEncodedContent(string username, string password, string? scope = null)
    {
        var metadata = new Dictionary<string, string>
        {
            { "grant_type", "password" },
            { "username", username },
            { "password", password },
            { "scope", scope ?? string.Empty }
        };

        return new FormUrlEncodedContent(metadata);
    }
}